package com.example.demo.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.example.demo.exception.ServiceException;
import com.example.demo.mapper.SysAttachinfoMapper;
import com.example.demo.model.core.ResultCode;
import com.example.demo.model.entity.SysAttachinfo;
import com.example.demo.model.form.FileInfoForm;
import com.example.demo.service.SysAttachService;
import com.mybatisflex.core.query.QueryWrapper;
import io.minio.*;
import io.minio.http.Method;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * MinIO 文件上传服务类
 */
@Component
@ConditionalOnProperty(value = "oss.type", havingValue = "minio")
@ConfigurationProperties(prefix = "oss.minio")
@Data
@Slf4j
public class SysAttachMinioImplService implements SysAttachService {

    /**
     * 服务Endpoint
     */
    private String endpoint;
    /**
     * 访问凭据
     */
    private String accessKey;
    /**
     * 凭据密钥
     */
    private String secretKey;
    /**
     * 存储桶名称
     */
    private String bucketName;
    /**
     * 自定义域名
     */
    private String customDomain;

    private MinioClient minioClient;
    @Autowired
    private SysAttachinfoMapper sysAttachinfoMapper;

    // 依赖注入完成之后执行初始化
    @PostConstruct
    public void init() {
        minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
        // 创建存储桶(存储桶不存在)
        // createBucketIfAbsent(bucketName);
    }

    /**
     * 上传文件
     *
     * @param file 表单文件对象
     * @return 文件信息
     */
    @Override
    public FileInfoForm uploadFile(MultipartFile file) {

        // 创建存储桶(存储桶不存在)，如果有搭建好的minio服务，建议放在init方法中
        createBucketIfAbsent(bucketName);

        // 文件原生名称
        String originalFilename = file.getOriginalFilename();
        // 文件后缀
        String suffix = FileUtil.getSuffix(originalFilename);
        // 文件夹名称
        String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd");
        // 文件名称
        String attachGuid = IdUtil.simpleUUID();

        // try-with-resource 语法糖自动释放流
        try (InputStream inputStream = file.getInputStream()) {
            // 文件上传
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(attachGuid)
                    .contentType(file.getContentType()).stream(inputStream, inputStream.available(), -1).build();
            minioClient.putObject(putObjectArgs);

            // 返回文件路径
            String fileUrl;
            // 未配置自定义域名
            if (StrUtil.isBlank(customDomain)) {
                // 获取文件URL
                GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
                        .bucket(bucketName).object(attachGuid).method(Method.GET).build();

                fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
                fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
            } else {
                // 配置自定义文件路径域名
                fileUrl = customDomain + "/" + bucketName + "/" + attachGuid;
            }
            int length = inputStream.available();
            SysAttachinfo attachinfo = new SysAttachinfo();
            attachinfo.setAttachguid(attachGuid);
            attachinfo.setAttachfilename(originalFilename);
            attachinfo.setContenttype(file.getContentType());
            attachinfo.setAttachlength(length);
            attachinfo.setStoragetype("minio");
            attachinfo.setUpdatetime(new Date());
            attachinfo.setFilepath(fileUrl);
            sysAttachinfoMapper.insert(attachinfo);

            FileInfoForm fileInfo = new FileInfoForm();
            fileInfo.setName(originalFilename);
            fileInfo.setUrl(fileUrl);
            fileInfo.setAttachGuid(attachGuid);
            return fileInfo;
        } catch (Exception e) {
            log.error("上传文件失败", e);
            throw new ServiceException(ResultCode.UPLOAD_FILE_EXCEPTION);
        }
    }

    /**
     * 删除文件
     *
     * @param attachGuid 文件完整路径
     * @return 是否删除成功
     */
    @Override
    public boolean deleteFile(String attachGuid) {
        try {
            sysAttachinfoMapper.deleteById(attachGuid);
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucketName).object(attachGuid).build();
            minioClient.removeObject(removeObjectArgs);
            return true;
        } catch (Exception e) {
            log.error("删除文件失败", e);
            throw new ServiceException(ResultCode.DELETE_FILE_EXCEPTION);
        }
    }

    /**
     * PUBLIC桶策略
     * 如果不配置，则新建的存储桶默认是PRIVATE，则存储桶文件会拒绝访问 Access Denied
     *
     * @param bucketName 存储桶名称
     * @return 存储桶策略
     */
    private static String publicBucketPolicy(String bucketName) {
        // AWS的S3存储桶策略 JSON 格式
        // https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example-bucket-policies.html
        return "{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\","
                + "\"Principal\":{\"AWS\":[\"*\"]},"
                + "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"],"
                + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]}," + "{\"Effect\":\"Allow\","
                + "\"Principal\":{\"AWS\":[\"*\"]},"
                + "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],"
                + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
    }

    /**
     * 创建存储桶(存储桶不存在)
     *
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    private void createBucketIfAbsent(String bucketName) {
        BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
        if (!minioClient.bucketExists(bucketExistsArgs)) {
            MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();

            minioClient.makeBucket(makeBucketArgs);

            // 设置存储桶访问权限为PUBLIC， 如果不配置，则新建的存储桶默认是PRIVATE，则存储桶文件会拒绝访问 Access
            // Denied
            SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder().bucket(bucketName)
                    .config(publicBucketPolicy(bucketName)).build();
            minioClient.setBucketPolicy(setBucketPolicyArgs);
        }
    }

    @Override
    public InputStream downloadFile(String attachGuid) {
        throw new UnsupportedOperationException("minio 未支持");
    }

    @Override
    public void updateFileClientinfo(List<String> attachGuidList, String clientGuid) {
        List<SysAttachinfo> attachList = sysAttachinfoMapper.selectListByQuery(QueryWrapper.create().in("attachguid", attachGuidList));
        for (SysAttachinfo attachinfo : attachList) {
            attachinfo.setClientguid(clientGuid);
            sysAttachinfoMapper.update(attachinfo);
        }
    }

    @Override
    public void updateFileClientinfo(List<String> attachGuidList, String clientGuid, String clientTag) {
        List<SysAttachinfo> attachList = sysAttachinfoMapper.selectListByQuery(QueryWrapper.create().in("attachguid", attachGuidList));
        for (SysAttachinfo attachinfo : attachList) {
            attachinfo.setClientguid(clientGuid);
            attachinfo.setClienttag(clientTag);
            sysAttachinfoMapper.update(attachinfo);
        }
    }
}
